/**
 *
 */
package cn.hg.solon.youcan.job.provider;

import cn.hg.solon.youcan.common.enums.BeanStatus;
import cn.hg.solon.youcan.common.exception.ServiceException;
import cn.hg.solon.youcan.easyquery.util.EntityQueryableUtil;
import cn.hg.solon.youcan.job.constant.JobStatus;
import cn.hg.solon.youcan.job.entity.EqJob;
import cn.hg.solon.youcan.job.entity.Job;
import cn.hg.solon.youcan.job.entity.proxy.EqJobProxy;
import cn.hg.solon.youcan.job.service.JobService;
import cn.hg.solon.youcan.job.utils.AbstractCronJob;
import cn.hg.solon.youcan.job.utils.CronDisallowConcurrentExecution;
import cn.hg.solon.youcan.job.utils.CronJobExecution;
import cn.hg.solon.youcan.job.utils.CronJobUtil;
import com.easy.query.api.proxy.client.EasyEntityQuery;
import com.easy.query.api.proxy.entity.select.EntityQueryable;
import com.easy.query.core.api.pagination.EasyPageResult;
import com.easy.query.solon.annotation.Db;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.bean.copier.CopyOptions;
import org.dromara.hutool.core.convert.ConvertUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.text.StrValidator;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.cron.CronUtil;
import org.dromara.hutool.cron.pattern.CronPattern;
import org.dromara.hutool.cron.task.Task;
import org.dromara.hutool.db.Page;
import org.dromara.hutool.db.PageResult;
import org.noear.solon.annotation.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;

/**
 * 定时任务服务实现
 *
 * @author 胡高
 */
@Component
public class EqJobProvider implements JobService {
    protected Logger log = LoggerFactory.getLogger(this.getClass());

    @Db("db1")
    private EasyEntityQuery easyEntityQuery;

    private EntityQueryable<EqJobProxy, EqJob> buildQuery(Page page, Map<String, Object> paraMap) {
        String word = (String) paraMap.get("word");
        String group = (String) paraMap.get("group");

        EntityQueryable<EqJobProxy, EqJob> entityQueryable = this.easyEntityQuery
                // FROM sys_job AS t
                .queryable(EqJob.class)
                // WHERE t.`group` = ${group}
                .where(t -> {
                    t.group().eq(StrValidator.isNotBlank(group), group);
                    // AND (t.`name` LIKE '%${word}%' OR t.`target` LIKE '%${word}%' OR t.`cron` LIKE '%${word}%')
                    t.or(StrValidator.isNotBlank(word), () -> {
                        t.name().like(word);
                        t.target().like(word);
                        t.cron().like(word);
                    });
                });
        // ORDER BY ${sortField}
        return EntityQueryableUtil.applyOrderBy(entityQueryable, page);
    }


    private boolean checkCron(Job bean) {
        try {
            CronPattern.of(bean.getCron());
            return true;
        } catch (Throwable e) {
            return false;
        }
    }

    @Override
    public boolean checkUnique(Job bean) {
        return !this.easyEntityQuery
                // FROM sys_job AS t
                .queryable(EqJob.class)
                // WHERE t.`group` = ${bean.group} AND t.`name` = ${bean.name} AND t.`id` <> ${bean.id}
                .where(t -> {
                    t.group().eq(bean.getGroup());
                    t.name().eq(bean.getName());
                    t.id().ne(ObjUtil.isNotNull(bean.getId()), bean.getId());
                })
                .any();
    }

    private AbstractCronJob createTask(Job job) {
        /*
         * 克隆Job
         */
        Job tempJob = BeanUtil.copyProperties(job, Job.class);

        if (tempJob.getIsConcurrent()) {
            return new CronJobExecution(tempJob);
        }

        return new CronDisallowConcurrentExecution(tempJob);
    }

    @Override
    public boolean delete(List<Integer> idList) {
        /*
         * 删除前停止任务
         */
        for (Integer id : idList) {
            Job bean = this.get(id);
            this.pause(bean);
        }

        return this.easyEntityQuery.deletable(EqJob.class)
                .whereByIds(idList)
                .allowDeleteStatement(true)
                .executeRows() > 0L;
    }

    @Override
    public boolean destroy() {
        this.stopCron();
        return true;
    }

    @Override
    public Job get(Integer id) {
        return this.easyEntityQuery.queryable(EqJob.class).whereById(id).firstOrNull();
    }

    @Override
    public void init() throws ServiceException {
        /*
         * 如果Cron服务没有启动，则启动之
         */
        List<EqJob> jobList = this.easyEntityQuery.queryable(EqJob.class).toList();
        for (Job job : jobList) {
            if (CharSequenceUtil.equals(job.getStatus(), JobStatus.OFF.name())) {
                this.pause(job);
            } else if (CharSequenceUtil.equals(job.getStatus(), JobStatus.ON.name())) {
                this.resume(job);
            }
        }
    }

    @Override
    public boolean insert(Job bean) {
        if (!this.checkUnique(bean)) {
            throw new ServiceException("任务名称已经存在，请更换其它值！");
        }

        if (!this.checkCron(bean)) {
            throw new ServiceException("Cron表达式校验不通过，请更换其它值！");
        }

        EqJob cloneBean = BeanUtil.copyProperties(bean, EqJob.class);

        return this.easyEntityQuery.insertable(cloneBean).executeRows() > 0L;
    }

    @Override
    public PageResult<? extends Job> pageBy(Page page, Map<String, Object> paraMap) {
        EasyPageResult<EqJob> pageList = this.buildQuery(page, paraMap).toPageResult(page.getPageNumber(), page.getPageSize());

        PageResult<EqJob> result = new PageResult<>();
        result.addAll(pageList.getData());
        result.setTotal(ConvertUtil.toInt(pageList.getTotal()));

        return result;
    }

    @Override
    public boolean pause(Job bean) {
        /*
         * 生成任务Key
         */
        String key = CronJobUtil.genTaskKey(bean);

        /*
         * 无任务
         */
        if (ObjUtil.isNull(CronUtil.getScheduler().getTask(key))) {
            return true;
        }

        /*
         * 有任务，则移除之
         */
        CronUtil.getScheduler().deschedule(key);

        EqJob exist = (EqJob) this.get(bean.getId());
        if (!CharSequenceUtil.equals(exist.getStatus(), JobStatus.OFF.name())) {
            exist.setStatus(JobStatus.OFF.name());
            this.update(exist);
        }

        this.log.debug("任务 [{}] 停止成功。。。", exist.getName());

        return true;
    }

    @Override
    public boolean resume(Job bean) {
        /*
         * 生成任务Key
         */
        String key = CronJobUtil.genTaskKey(bean);

        this.startCron();

        /*
         * 无任务，则创建之
         */
        if (ObjUtil.isNull(CronUtil.getScheduler().getTask(key))) {
            /*
             * 创建Task
             */
            Task task = this.createTask(bean);

            CronUtil.getScheduler().schedule(key, bean.getCron(), task);

            EqJob exist = (EqJob) this.get(bean.getId());
            if (!CharSequenceUtil.equals(exist.getStatus(), BeanStatus.ON.name())) {
                exist.setStatus(BeanStatus.ON.name());
                this.update(exist);
            }
            this.log.debug("任务 [{}] 启动成功。。。", bean.getName());
        } else {
            /*
             * 有任务，则更新之
             */
            CronUtil.getScheduler().updatePattern(key, CronPattern.of(bean.getCron()));

            this.log.debug("任务 [{}] 更新成功。。。", bean.getName());
        }

        return true;
    }

    private void startCron() {
        if (!CronUtil.getScheduler().isStarted()) {
            CronUtil.start();
        }
    }

    private void stopCron() {
        if (CronUtil.getScheduler().isStarted()) {
            CronUtil.stop();
        }
    }

    @Override
    public boolean test(Job bean) {
        // 执行测试时，必须写调度日志
        bean.setIsLog(true);

        /*
         * 创建Task
         */
        Task task = this.createTask(bean);

        /*
         * 执行任务（单次）
         */
        try {
            task.execute();
            return true;
        } catch (Throwable e) {
            return false;
        }
    }

    @Override
    public boolean update(Job bean) {
        if (!this.checkUnique(bean)) {
            throw new ServiceException("任务名称已经存在，请更换其它值！");
        }

        if (!this.checkCron(bean)) {
            throw new ServiceException("Cron表达式校验不通过，请更换其它值！");
        }

        if (StrUtil.equals(bean.getStatus(), BeanStatus.OFF.name())) {
            // 停止定时任务
            this.pause(bean);
        }

        EqJob cloneBean = (EqJob) this.get(bean.getId());

        BeanUtil.copyProperties(bean, cloneBean, CopyOptions.of().setIgnoreNullValue(true));

        return this.easyEntityQuery.updatable(cloneBean).executeRows() > 0L;
    }
}
